package com.aphrodite.util.regex;

/**
 * A class that holds compiled regular expressions. You should not need to work
 * directly with this class.
 * 
 */
class RegExProgram {
	static final int OPT_HASBACKREFS = 1;
	static final int OPT_HASBOL = 2;

	int flags; // Optimization flags (REProgram.OPT_*)
	char[] instruction; // The compiled regular expression 'program'
	private int lenInstruction; // The amount of the instruction buffer in use
	int maxParens = -1;
	char[] prefix; // Prefix string optimization

	/**
	 * Constructs a program object from a character array
	 * 
	 * @param instruction
	 *            Character array with RE opcode instructions in it
	 */
	RegExProgram(char[] instruction) {
		this(instruction, instruction.length);
	}

	/**
	 * Constructs a program object from a character array
	 * 
	 * @param instruction
	 *            Character array with RE opcode instructions in it
	 * @param lenInstruction
	 *            Amount of instruction array in use
	 */
	RegExProgram(char[] instruction, int lenInstruction) {
		setInstructions(instruction, lenInstruction);
	}

	/**
	 * Constructs a program object from a character array
	 * 
	 * @param parens
	 *            Count of parens in the program
	 * @param instruction
	 *            Character array with RE opcode instructions in it
	 */
	RegExProgram(int parens, char[] instruction) {
		this(instruction, instruction.length);
		this.maxParens = parens;
	}

	/**
	 * Returns a copy of the current regular expression program in a character
	 * array that is exactly the right length to hold the program. If there is
	 * no program compiled yet, getInstructions() will return null.
	 * 
	 * @return A copy of the current compiled RE program
	 */
	private char[] getInstructions() {
		// Ensure program has been compiled!
		if (lenInstruction != 0) {
			// Return copy of program
			char[] ret = new char[lenInstruction];
			System.arraycopy(instruction, 0, ret, 0, lenInstruction);
			return ret;
		}
		return null;
	}

	/**
	 * Returns a copy of the prefix of current regular expression program in a
	 * character array. If there is no prefix, or there is no program compiled
	 * yet, <code>getPrefix</code> will return null.
	 * 
	 * @return A copy of the prefix of current compiled RE program
	 */
	private char[] getPrefix() {
		if (prefix != null) {
			// Return copy of prefix
			char[] ret = new char[prefix.length];
			System.arraycopy(prefix, 0, ret, 0, prefix.length);
			return ret;
		}
		return null;
	}

	/**
	 * Sets a new regular expression program to run. It is this method which
	 * performs any special compile-time search optimizations. Currently only
	 * two optimizations are in place - one which checks for backreferences (so
	 * that they can be lazily allocated) and another which attempts to find an
	 * prefix anchor string so that substantial amounts of input can potentially
	 * be skipped without running the actual program.
	 * 
	 * @param instruction
	 *            Program instruction buffer
	 * @param lenInstruction
	 *            Length of instruction buffer in use
	 */
	private void setInstructions(char[] instruction, int lenInstruction) {
		// Save reference to instruction array
		this.instruction = instruction;
		this.lenInstruction = lenInstruction;

		// Initialize other program-related variables
		this.flags = 0;
		this.prefix = null;

		// Try various compile-time optimizations if there's a program
		if (instruction != null && lenInstruction != 0) {
			// If the first node is a branch
			if (lenInstruction >= RegEx.nodeSize && instruction[0 + RegEx.offsetOpcode] == RegEx.OP_BRANCH) {
				// to the end node
				int next = (short) instruction[0 + RegEx.offsetNext];
				if (instruction[next + RegEx.offsetOpcode] == RegEx.OP_END && lenInstruction >= (RegEx.nodeSize * 2)) {
					final char nextOp = instruction[RegEx.nodeSize + RegEx.offsetOpcode];
					// the branch starts with an atom
					if (nextOp == RegEx.OP_ATOM) {
						// then get that atom as an prefix because there's no
						// other choice
						int lenAtom = instruction[RegEx.nodeSize + RegEx.offsetOpdata];
						this.prefix = new char[lenAtom];
						System.arraycopy(instruction, RegEx.nodeSize * 2, prefix, 0, lenAtom);
					}
					// the branch starts with a BOL
					else if (nextOp == RegEx.OP_BOL) {
						// then set the flag indicating that BOL is present
						this.flags |= OPT_HASBOL;
					}
				}
			}

			BackrefScanLoop:

			// Check for backreferences
			for (int i = 0; i < lenInstruction; i += RegEx.nodeSize) {
				switch (instruction[i + RegEx.offsetOpcode]) {
				case RegEx.OP_ANYOF:
					i += (instruction[i + RegEx.offsetOpdata] * 2);
					break;

				case RegEx.OP_ATOM:
					i += instruction[i + RegEx.offsetOpdata];
					break;

				case RegEx.OP_BACKREF:
					flags |= OPT_HASBACKREFS;
					break BackrefScanLoop;
				}
			}
		}
	}
}
